import { Store, Level, Role, User, Authority } from "../interface"
import { Collection, MongoClient, ObjectId } from 'mongodb'
import { MD5 } from "../utils"
export interface MongoStoreOption {
    authorities: {
        tablename: string
        columns: {
            id: '_id'
            name: string
        }
    }
    roles: {
        tablename: string
        columns: {
            id: '_id'
            name: string
            /**
             * 是否为系统用户
             */
            isSys: string
            authorities: string
            is_admin?: string
        }
    }
    users: {
        tablename: string
        columns: {
            id: '_id'
            /**
             * 账号
             */
            name: string
            /**
             * 昵称
             */
            nickname: string
            role_id: string
            password: string
            /**
             * 是否为系统用户
             */
            isSys?: string
            /**
             * 允许登录的ip
             */
            lock_ips?: string
            /**
             * 过期时间
             */
            expires?: string
        }
    }
}
const defaultMongoStoreOption: MongoStoreOption = {
    authorities: {
        tablename: 'authorities',
        columns: {
            id: '_id',
            name: 'name'
        }
    },
    roles: {
        tablename: 'roles',
        columns: {
            id: '_id',
            name: 'name',
            is_admin: 'isAdmin',
            isSys: 'isSys',
            authorities: 'authorities'
        }
    },
    users: {
        tablename: 'users',
        columns: {
            id: '_id',
            name: 'name',
            nickname: 'nickname',
            role_id: 'roleId',
            password: 'password',
            isSys: 'isSys',
            lock_ips: 'lock_ips'
        }
    }
};

export interface MongoDbConfig {
    url: string
    dbName: string
}

class DBCenter {
    private __client: MongoClient
    private dbName: string
    constructor(config: MongoDbConfig) {
        this.__client = new MongoClient(config.url)
        this.dbName = config.dbName
    }
    public async getCollection(doc: string) {
        const { __client, dbName } = this
        await __client.connect()
        return __client.db(dbName).collection(doc)
    }
}
export interface IMongoStore extends Store {
    new (config:MongoDbConfig, option:MongoStoreOption): this
}
export class MongoStore implements Store {
    createToken(acc: { name: any; password: string; }) {
        return MD5(`${acc.name}:${MD5(acc.password)}`)
    }
    
    option: MongoStoreOption
    config: MongoDbConfig
    dBCenter: DBCenter
    usePasswordChange = true
    __userMap: Map<string,User> = new Map<string,User>()
    __obj: {
        authorities: Authority[]
        roles: Role[]
        users: User[]
    } = {
        authorities: [],
        roles: [],
        users: []
    }
    constructor(config: MongoDbConfig, option = defaultMongoStoreOption) {
        this.option = option
        this.config = config
        this.dBCenter = new DBCenter(config)
        this.refresh()
    }
    __initUserMap = () => {
        this.__userMap = new Map()
        this.__obj.users.map(user => {
            const key = MD5(`${user.name}:${user.password}`)
            this.__userMap.set(key, {
                ...user,
                token: key,
                role: this.__obj.roles.find(r => JSON.stringify(r.id) === JSON.stringify(user.roleId) )
            })
        })
    }
    refresh = async () => {
        const authorities = await this.authorities()
        const roles = await this.roles()
        const users = await this.users()
        this.__obj = { authorities, roles, users }
        this.__initUserMap()
    }
    async authorities() {
        const { authorities } = this.option
        const conn = await this.dBCenter.getCollection(authorities.tablename)
        const data2 = await conn.find().toArray()
        const arr: Authority[] =  data2.map((item) => ({
            id: item._id.toString(),
            name: item[authorities.columns.name]
        }))
        return arr
    };
    async roles () {
        const { roles } = this.option
        const conn_roles = await this.dBCenter.getCollection(roles.tablename)
        const data = await conn_roles.find().toArray()
        return this.__obj.roles = data.map((item) => ({
            id: item._id.toString(),
            name: item[roles.columns.name],
            isAdmin: roles.columns.is_admin && item[roles.columns.is_admin],
            isSys: item[roles.columns.isSys],
            authorities: item[roles.columns.authorities]
        }))
    }
    async users() {
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        const usersarr = await conn.find().toArray()
        return this.__obj.users = usersarr.map((item) => ({
            id: item._id.toString(),
            name: item[users.columns.name],
            nickname: item[users.columns.nickname],
            password: item[users.columns.password],
            roleId: item[users.columns.role_id],
            lock_ips: users.columns.lock_ips && item[users.columns.lock_ips],
            isSys: users.columns.isSys && item[users.columns.isSys],
            expires: users.columns.expires && item[users.columns.expires]
        }))
    };
    async addRole(roleName: string) {
        const { roles } = this.option
        const conn = await this.dBCenter.getCollection(roles.tablename)
        let info = {}
        info[roles.columns.name] = roleName
        await conn.insertOne(info)
        this.refresh()
        return 1
    };
    async delRole(roleId: string) {
        const { roles } = this.option
        const conn = await this.dBCenter.getCollection(roles.tablename)
        let filters = {}
        filters[roles.columns.id] = new ObjectId(roleId)
        await conn.deleteOne(filters)
        this.refresh()
        return
    };
    async addUser(userName: string, password: string, nickname?: string) {
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let info = {}
        info[users.columns.name] = userName
        info[users.columns.nickname] = nickname
        info[users.columns.password] = MD5(password || userName)
        await conn.insertOne(info)
        this.refresh()
        return 1
    }
    async delUser(userId: string) {
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let filters = {}
        filters[users.columns.id] = new ObjectId(userId)
        await conn.deleteOne(filters)
        this.refresh()
        return
    };
    async changeUserPassword(userId: string, password?: string) {
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let filters = {}
        filters[users.columns.id] = new ObjectId(userId)
        let info = {}
        info[users.columns.password] = MD5(password || userId)
        await conn.updateOne(filters,{'$set':info})
        this.refresh()
        return
    };
    async updateRoleAuthority(roleId: string, authorityId: string, level: Level) {
        const {  roles } = this.option
        const conn = await this.dBCenter.getCollection(roles.tablename)
        let filters = { [roles.columns.id]: new ObjectId(roleId) }
        if(level === Level.NONE) {
            const role = await conn.findOne(filters)
            const authorities= role?.authorities
            await conn.updateOne(filters, { '$set': { authorities: { ...authorities, [authorityId]: 0 } } });
        } else {
            const role = await conn.findOne(filters)
            const data = await conn.updateOne(filters,{'$set':{
                authorities: Object.assign({}, role?.authorities, { [authorityId]: level})
            }})
        }
        this.refresh()
        return
    };
    async updateUserRole(userId: string, roleId){
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let filters = {}
        filters[users.columns.id] = new ObjectId(userId)
        let info = {}
        if(typeof roleId != "undefined") {
            info[users.columns.role_id] = new ObjectId(roleId)
            const data = await conn.updateOne(filters,{'$set':info})
        } else {
            info[users.columns.role_id] = null
            const data = await conn.updateOne(filters,{'$set':info})
        }
        this.refresh()
        return
    };
    /**
     * 用户指定ip登录
     * @param userId 用户id
     * @param ips 可登录的ip数组
     */
    async updateUserLockIPs(userId: string, ips: string[]){
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let filters = {}
        filters[users.columns.id] = new ObjectId(userId)
        let info = {}
        if (users.columns.lock_ips) {
            info[users.columns.lock_ips] = ips
        }
        await conn.updateOne(filters,{'$set':info})
        this.refresh()
        return
    };
    /**
     * 用户账号有效期
     * @param userId 用户id
     * @param expires 失效时间
     */
    async updateUserExpires(userId: string, expires: number) {
        const { users } = this.option
        const conn = await this.dBCenter.getCollection(users.tablename)
        let filters = {}
        filters[users.columns.id] = new ObjectId(userId)
        let info = {}
        if (users.columns.expires) {
            info[users.columns.expires] = expires
        }
        await conn.updateOne(filters,{'$set':info})
        this.refresh()
        return
    }
    getLoginUser = (key: string) => {
        return this.__userMap.get(key)
    }
    lookLoginUser = async (key: string) => {
        await this.refresh()
        return this.__userMap.get(key)
    }
}